#include <iostream>
#include <chrono>
#include <ctime>
#include <fstream>
#include <string>

#include <dirent.h>

// 添加OpenCV的头文件
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/face.hpp>

#define CVUI_IMPLEMENTATION // 该定义需要在一个（且只有一个）C++ 文件中使用
#include "cvui.h"

#include "getweather.hpp" // 获取天气信息
#include "TCPClient.hpp"  // TCP 客户端

#define WINDOW_NAME "Facial recognition login system" // 显示窗口的名字
#define CITY "guangzhou"                              // 需要查询天气的城市
#define PORT 8888
#define SERVER_IP "192.168.53.64"

using namespace std;
using namespace cv;
using namespace face;

string showUserLabel(Mat frame, int label);
void logFaceRecognition(const string &username, bool isSuccessful);
vector<Ptr<LBPHFaceRecognizer>> loadMultipleModels(const vector<string> &modelPaths);
vector<string> getModelPaths(const string &modelDirectory);

int main()
{
    Mat frame = Mat(480, 900, CV_8UC3),    // 主画布
        frameCam = Mat(480, 600, CV_8UC3), // 摄像头帧
        successImg = imread("../img/success.jpg"),
        warningImg = imread("../img/warning.jpg"),
        no_face = imread("../img/no_face.jpg"),
        frameFaces, // 初始人脸图像
        showFaces,  // 调整大小后人脸图像
        gray;       // 灰度图像

    int x, y, w, h;          // 人脸坐标
    int label = -1;          // 用户标签
    double confidence = 0.0; // 可信度
    double Similarity = 0.0; //  相似度

    chrono::system_clock::time_point popupStartTime;    // 弹窗开始时间
    chrono::system_clock::time_point faceDetectionTime; // 人脸检测时间
    bool showSuccessPopup = false;                      // 识别成功弹窗标识
    bool showWarningPopup = false;                      // 警告弹窗标识
    bool isPopupTimerStarted = false;                   // 弹窗显示计时器标识
    bool identifyTimer = false;                         // 识别计时器标识

    cout << "This face recognition client" << endl;

    // 获取当前时间点
    auto now = chrono::system_clock::now();
    // 转换为time_t类型
    time_t now_c = chrono::system_clock::to_time_t(now);
    // 转换为tm结构体
    tm now_tm = *localtime(&now_c);

    char time_str[100];
    char date_str[100];
    sprintf(date_str, "%d-%02d-%02d", now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday);
    sprintf(time_str, "%02d:%02d:%02d", now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec);

    cout << "now-date :" << date_str << endl;
    cout << "now-time :" << time_str << endl;

    // 获取循环开始时的时间点
    auto last_time = chrono::system_clock::now();

    // 获取天气信息
    string weatherInfo = getWeatherString(CITY);
    //    cout << weatherInfo << endl;

    // 将温度和天气数据提取出来
    string temperature, weather;
    extractWeatherAndTemperature(weatherInfo, temperature, weather);
    cout << temperature << endl;
    cout << weather << endl;

    // 初始化控制台编码
    initConsole();

    // 创建 TCP 客户端实例
    TCPClient tcpClient(SERVER_IP, PORT);

    // 启动连接线程
    tcpClient.startConnection();

    // 初始化摄像头对象
    VideoCapture cap(0);
    if (!cap.isOpened())
    {
        cerr << "ERROR! Unable to open camera" << endl;
        return -1;
    }

    // 定义一个检测器
    CascadeClassifier cascade;

    //  加载人脸检测器
    if (!cascade.load("E:/xuexi/text/C++/QTCode/opencv452/etc/haarcascades/haarcascade_frontalface_alt.xml"))
    {
        cerr << "ERROR: Could not load classifier cascade" << endl;
        return -1;
    }

    // 加载多个人脸模型
    vector<string> modelPaths = getModelPaths("../model/");
    auto models = loadMultipleModels(modelPaths);

    // 初始化cvui窗口
    cvui::init(WINDOW_NAME);

    while (true)
    {
        // 获取当前时间点
        auto now = chrono::system_clock::now();
        // 检查是否过去了一秒钟
        if (chrono::duration_cast<chrono::seconds>(now - last_time).count() >= 1)
        {
            // 更新时间
            last_time = now; // 更新last_time为当前时间
            time_t now_c = chrono::system_clock::to_time_t(now);
            tm now_tm = *localtime(&now_c);
            sprintf(time_str, "%02d:%02d:%02d", now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec);
        }

        // 使用颜色填充画布 BGR
        frame = cv::Scalar(255, 255, 255);

        // 读取采集的数据 放入 数据帧中
        cap >> frameCam;
        if (frameCam.empty())
        {
            cerr << "ERROR! Blank frameCam grabbed" << endl;
            break;
        }

        cvui::image(frame, 0, 0, frameCam);
        // 检测是否存在人脸
        vector<Rect> faces;

        // 检测人脸，并把检测到的结果 存入faces中
        cascade.detectMultiScale(frame, faces,
                                 1.1, 3, 0, Size(100, 100), Size(300, 300));

        // faces数组非空(检测到人脸)
        if (faces.size() > 0)
        {
            // 如果是第一次检测到人脸，记录开始时间
            if (!identifyTimer)
            {
                faceDetectionTime = chrono::system_clock::now();
                identifyTimer = true;
            }

            // 人脸出现的起始坐标
            x = faces.at(0).x;
            y = faces.at(0).y;

            // 人脸的宽度和高度
            w = faces.at(0).width;
            h = faces.at(0).height;

            // 渲染包围人脸的矩形框
            rectangle(frame, Point(x, y), Point(x + w, y + h),
                      Scalar(0, 255, 0), 2, LINE_8, 0);

            // 截取人脸
            Rect roi(x, y, w, h);
            frameFaces = frame(roi);
            // 调整显示人脸图像大小
            resize(frameFaces, showFaces, Size(260, 260));
            // 渲染人脸图像
            cvui::image(frame, 640, 150, showFaces);

            // 将彩色图像转换为灰度图像
            cvtColor(frameFaces, gray, COLOR_BGR2GRAY);

            // 遍历多个模型进行预测
            for (auto &model : models)
            {
                model->predict(gray, label, confidence);
                if (confidence < 45)
                {
                    // 找到匹配的模型
                    break;
                }
            }

            // cout << "label = " << label << " "
            //      << "Confidence = " << confidence << endl;

            // 显示相似度
            cvui::text(frame, 690, 460, "Similarity :", 0.4, 0x000000);
            Similarity = 100 - confidence;
            char str[10];
            sprintf(str, "%.2f%%", Similarity);
            cvui::text(frame, 770, 460, str, 0.4, 0x000000);

            // 检查是否超过5秒未通过识别
            auto timeSinceFaceDetection = chrono::duration_cast<chrono::seconds>(
                                              chrono::system_clock::now() - faceDetectionTime)
                                              .count();
            if (timeSinceFaceDetection >= 5 && !showSuccessPopup && !showWarningPopup)
            {
                showWarningPopup = true;                      // 警告弹窗显示
                popupStartTime = chrono::system_clock::now(); // 设置开始时间
                isPopupTimerStarted = true;                   // 计时器已启动
            }

            // 识别通过
            if (Similarity > 55 && !showSuccessPopup && !isPopupTimerStarted)
            {
                // 检查是否超过两秒
                auto timeSinceFaceDetection = chrono::duration_cast<chrono::seconds>(
                                                  chrono::system_clock::now() - faceDetectionTime)
                                                  .count();

                if (timeSinceFaceDetection >= 2)
                {
                    showSuccessPopup = true;                      // 识别成功弹窗显示
                    popupStartTime = chrono::system_clock::now(); // 设置开始时间
                    isPopupTimerStarted = true;                   // 计时器已启动
                }
            }
        }
        else
            cvui::image(frame, 640, 150, no_face); // 没有检测到人脸

        // 显示日期和时间
        cvui::text(frame, 670, 40, date_str, 0.4, 0x000000);
        cvui::text(frame, 780, 40, time_str, 0.4, 0x000000);

        // 显示天气和温度
        cvui::text(frame, 670, 80, temperature, 0.4, 0x000000);
        cvui::text(frame, 750, 80, weather, 0.4, 0x000000);

        // 当识别成功后显示图片弹窗
        if (showSuccessPopup)
        {
            cvui::image(frame, 300, 140, successImg);      // 显示成功图片
            string username = showUserLabel(frame, label); // 显示用户标签

            // 检查是否需要关闭弹窗（2秒后）
            auto currentTime = chrono::system_clock::now();
            auto elapsedTime = chrono::duration_cast<chrono::milliseconds>(
                                   currentTime - popupStartTime)
                                   .count();

            if (elapsedTime > 2000) // 2000毫秒
            {
                // 重置弹窗和定时器标志
                showSuccessPopup = false;
                isPopupTimerStarted = false;
                identifyTimer = false;

                logFaceRecognition(username, true); // 写入日志
            }
        }

        // 警告(识别未通过)弹窗
        if (showWarningPopup)
        {
            cvui::image(frame, 280, 140, warningImg); // 显示警告图片

            // 文字警告
            cvui::text(frame, 280, 200, "Warning: Face Not Recognized!", 0.7, 0xFF0000);

            // 检查是否需要关闭弹窗（2秒后）
            auto currentTime = chrono::system_clock::now();
            auto elapsedTime = chrono::duration_cast<chrono::milliseconds>(
                                   currentTime - popupStartTime)
                                   .count();

            if (elapsedTime > 2000) // 2000毫秒
            {
                // 重置弹窗和定时器标志
                showWarningPopup = false;
                isPopupTimerStarted = false;
                identifyTimer = false;

                logFaceRecognition("", false); // 写入日志
            }
        }

        // 更新组件
        cvui::update();

        // 设置窗口的名字并显示窗口
        imshow(WINDOW_NAME, frame);

        int key = waitKey(5);
        if (key == 13) // 按下键盘  回车发送数据
        {
            // 客户端数据发送测试
            tcpClient.sendData("Hello from client");
        }
        if (key == 27) // 按下键盘  ESC 退出
        {
            cout << "ESC key pressed. Exiting..." << endl;
            tcpClient.stopConnection(); // 断开TCP连接
            break;
        }
    }

    return 0;
}

// 显示当前用户标签(名字)
string showUserLabel(Mat frame, int label)
{
    ifstream file("../face_data/face_data.txt");
    string line, userLabel;
    stringstream ss;

    // 将label转换为字符串
    std::string labelStr = std::to_string(label);
    if (file.is_open())
    {
        while (getline(file, line))
        {
            // 检查行的第一个字符是否与标签对应
            if (line.find(labelStr) == 0)
            {
                // 找到'_'的位置
                size_t pos = line.find('_');
                if (pos != string::npos)
                {
                    // 提取'_'后面的字符串
                    userLabel = line.substr(pos + 1);
                    userLabel.erase(userLabel.find_last_not_of("\n") + 1);
                    ss << "User: " << userLabel;
                    break; // 找到匹配的标签后退出循环
                }
            }
        }
        file.close();
    }
    else
    {
        cout << "User Label: File not found or unable to open" << endl;
    }
    // cout << ss.str() << endl;
    cvui::text(frame, 350, 200, ss.str(), 0.7, 0x000000);

    return userLabel;
}

// 日志记录函数
void logFaceRecognition(const string &username, bool isSuccessful)
{
    // 获取当前时间
    auto now = chrono::system_clock::now();
    time_t now_c = chrono::system_clock::to_time_t(now);
    tm now_tm = *localtime(&now_c);

    // 格式化日期和时间
    char datetime_str[100];
    sprintf(datetime_str, "%d-%02d-%02d %02d:%02d:%02d",
            now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday,
            now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec);

    // 打开日志文件，追加模式
    ofstream logFile("../face_data/log.txt", ios::app);
    if (logFile.is_open())
    {
        // 写入日志记录
        logFile << datetime_str << " | "
                << (isSuccessful ? "SUCCESS" : "FAILED") << " | "
                << (username.empty() ? "Unknown User" : username)
                << endl;
        logFile.close();
    }
    else
    {
        cerr << "Unable to open log file" << endl;
    }
}

// 加载多个模型
vector<Ptr<LBPHFaceRecognizer>> loadMultipleModels(const vector<string> &modelPaths)
{
    vector<Ptr<LBPHFaceRecognizer>> models;
    for (const auto &path : modelPaths)
    {
        Ptr<LBPHFaceRecognizer> model = LBPHFaceRecognizer::create();
        model->read(path);
        models.push_back(model);
    }
    return models;
}

// 获取模型路径
vector<string> getModelPaths(const string &modelDirectory)
{
    vector<string> modelPaths;
    DIR *dir;
    struct dirent *ent;

    if ((dir = opendir(modelDirectory.c_str())) != NULL)
    {
        while ((ent = readdir(dir)) != NULL)
        {
            string filename = ent->d_name;
            if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".xml")
            {
                modelPaths.push_back(modelDirectory + filename);
            }
        }
        closedir(dir);
    }
    else
    {
        cerr << "Could not open directory" << endl;
    }

    return modelPaths;
}
